﻿#include "GraphicsView.h"
#include "GraphicsItem.h"
#include "GraphicsScene.h"

#include "PortItem.h"
#include "IndicateLineItem.h"
#include "LineItem.h"

#include "FITK_Kernel/FITKAppFramework/FITKAppFramework.h"
#include "FITK_Kernel/FITKAppFramework/FITKGlobalData.h"

#include "DataCenter/FrameAppData.h"
#include "DataCenter/LogData.h"
#include "DataCenter/MultiSimulationAppData.h"
#include "DataCenter/ChannelDegenerate.h"
#include "DataCenter/SingleSimulationAppData.h"

#include "OperatorsInterface/GraphicsObjectEventOperator.h"

#include <QScrollBar>
#include <QKeyEvent>
#include <QMimeData>
#include <QGraphicsSceneMouseEvent>

namespace Graphics
{

    class ViewPrivater
    {
    public:
        void initView(GraphicsView * view)
        {
            view->setDragMode(QGraphicsView::RubberBandDrag);
            view->setAcceptDrops(true);
            view->setCursor(Qt::ArrowCursor);

            /// initialize data manger
            auto appData = FITKAPP->getGlobalData()->getData<Data::FrameAppData>(FRAME_APP_DATA_ID);
            if (!appData) { return; }
            _cptAppDataMgr = appData->getComponentAppDataManger();
            _sglSimAppDataMgr = appData->getSingleSimulationAppDataManger();
            _mulSimAppDataMgr = appData->getMultiSimulationAppDataManger();
            _lineDataMgr = appData->getLineDataManger();

        }

        /**
         * @brief     canvas state
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:04
         */
        State _state;

        /**
         * @brief     while draw alignment line. use to know if left button down and select graphics item move
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:07
         */
        bool _leftButtonDownAndSelectedItem{ false };

        /**
         * @brief     while draw alignment line. if < _tolerationDistance. draw alignment line
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:08
         */
        int _tolerationDistance{ 8 };

        /**
         * @brief     first selected item. used for alignment
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:14
         */
        GraphicsItem * _firstSelectedItem{};

        /**
         * @brief     view port ratio
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:14
         */
        double _viewportRatio{ 1.0 };

        /**
         * @brief     component app data manger
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:15
         */
        Data::ComponentAppDataManger * _cptAppDataMgr{};

        /**
         * @brief     single simulation app data manger
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:15
         */
        Data::SingleSimulationAppDataManger * _sglSimAppDataMgr{};

        /**
         * @brief     line data manger
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:16
         */
        Data::LineDataManger * _lineDataMgr{};

        /**
         * @brief     multi simulation app data manger
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:16
         */
        Data::MultiSimulationAppDataManger * _mulSimAppDataMgr{};

        /**
         * @brief     nearby judgment distance
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-8-31 13:09
         */
        int _sensitiveDistance{ 5 };

        /**
         * @brief     indicate line item . only one exists
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-2 10:44
         */
        IndicateLineItem * _line{};
    };

    GraphicsView::GraphicsView(QWidget *parent /*= nullptr*/)
        : QGraphicsView(parent)
        , _private(new ViewPrivater())
    {
        _private->initView(this);
        setStyleSheet(loadQSS(":/frames/GraphicsView.qss"));
    }

    GraphicsView::GraphicsView(QGraphicsScene *scene, QWidget *parent /*= nullptr*/)
        : QGraphicsView(scene, parent)
        , _private(new ViewPrivater())
    {
        _private->initView(this);
        setStyleSheet(loadQSS(":/frames/GraphicsView.qss"));
    }

    GraphicsView::~GraphicsView()
    {
        if (_private)
        {
            delete _private;
            _private = nullptr;
        }
    }

    State GraphicsView::state() const
    {
        return _private->_state;
    }

    void GraphicsView::setState(const State & state)
    {
        _private->_state = state;
    }

    int GraphicsView::sensitiveDistance() const
    {
        return _private->_sensitiveDistance;
    }

    void GraphicsView::setSensitiveDistance(int distance)
    {
        _private->_sensitiveDistance = distance;
        /// because sensitive distance used with mouse move. no need to update
    }

    void GraphicsView::dragEnterEvent(QDragEnterEvent *event)
    {
        QGraphicsView::dragEnterEvent(event);
        const QMimeData * mime = event->mimeData();
        QString byte = QString(mime->data("ComponentName"));
        if (!byte.isEmpty())
            event->accept();
        else
            event->ignore();
    }

    void GraphicsView::dragMoveEvent(QDragMoveEvent *event)
    {
        Q_UNUSED(event);
    }

    void GraphicsView::dropEvent(QDropEvent *event)
    {
        /// get component name
        const QMimeData * mime = event->mimeData();
        QString componentName = QString(mime->data("ComponentName"));

        /// add component item on canvas
        GraphicsItem * item = new GraphicsItem(componentName);
        QRectF rect = item->boundingRect();
        item->setPos(mapToScene(event->pos()) - QPointF(rect.width() / 2, rect.height() / 2));
        item->setZValue(1);
        scene()->addItem(item);
        auto items = scene()->items();

        /// add single simulation app data
        _private->_cptAppDataMgr->appendDataObj(item->componentAppData());
        Data::SingleSimulationAppData * singleApp = new Data::SingleSimulationAppData();
        singleApp->copy(item->componentAppData());
        _private->_sglSimAppDataMgr->appendDataObj(singleApp);

        /// add log
        QString log = QString("Add Component App %1 %2 at %3,%4").
            arg(componentName).
            arg(ADDR(item)).
            arg(item->pos().x()).
            arg(item->pos().y());
        LOG(log);
    }

    void GraphicsView::mouseMoveEvent(QMouseEvent *event)
    {
        QGraphicsView::mouseMoveEvent(event);
        QPointF eventScenePoint = mapToScene(event->pos());

        if (_private->_state.mode == Select_Mode)
        {
        }
        else if (_private->_state.mode == Line_Mode)
        {
            /// if line state is initialize state. while mouse move. state is changed to need select from node state.
            if (_private->_state.lineState == Line_Initial_State ||
                _private->_state.lineState == Line_Need_Select_From_State)
            {
                /// while mouse nearby. graphics item output port part show
                mouseMoveNearByOutputPort(eventScenePoint);
            }
            else if (_private->_state.lineState == Line_Need_Select_To_State)
            {
                /// while mouse nearby. graphics item input port part show
                mouseMoveNearByInputPort(eventScenePoint);

                /// update indicate line item cursor position. in item coordinate
                if (_private->_line)
                    _private->_line->setCursorPos(eventScenePoint - _private->_line->pos());
            }
        }
    }

    void GraphicsView::mousePressEvent(QMouseEvent *event)
    {
        QList<QGraphicsItem *> all = items();
        QGraphicsView::mousePressEvent(event);
        if (Qt::LeftButton == event->button())
        {
            if (Line_Mode == _private->_state.mode)
            {
                if (Line_Initial_State == _private->_state.lineState ||
                    Line_Need_Select_From_State == _private->_state.lineState)
                {
                    /// in line mode. select output port as line 's begin
                    mousePressSelectOutputPort();
                }
                else if (Line_Need_Select_To_State == _private->_state.lineState)
                {
                    /// in line mode. select output port as line 's end
                    mousePressSelectInputPort();
                }
            }
            else if (Select_Mode == _private->_state.mode)
            {

            }
        }
        else if (Qt::RightButton == event->button())
        {
            if (Line_Mode == _private->_state.mode)
            {
                /// if has indicate line. cancel line
                mousePressCancelLine();
            }
        }
    }

    void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
    {
        QGraphicsView::mouseReleaseEvent(event);
    }

    void GraphicsView::keyPressEvent(QKeyEvent *event)
    {
        if (event->key() == Qt::Key_Delete)
        {
            auto items = scene()->selectedItems();
            dataUpdateByRemoveItems(items);
            int iNum = items.size();
            for (int iCnt = 0; iCnt < iNum; ++iCnt)
            {
                auto item = items[iCnt];
                scene()->removeItem(item);
                delete item;
                item = nullptr;
            }
        }
        QGraphicsView::keyPressEvent(event);
    }

    void GraphicsView::mouseMoveNearByInputPort(QPointF mousePoint)
    {
        QList<QGraphicsItem *> all = items();
        for (QGraphicsItem * one : all)
        {
            /// get item port part
            GraphicsItem * item = dynamic_cast<GraphicsItem *>(one);
            if (!item) { continue; }
            PortItem * port = item->portPart();
            if (!port) { continue; }

            /// is mouse nearby item input port. manage input port state
            QPointF portPoint = port->scenePos();
            QRectF inputPortRect(
                portPoint.x() - _private->_sensitiveDistance,
                portPoint.y() - _private->_sensitiveDistance,
                port->size() + _private->_sensitiveDistance * 2,
                port->size() + _private->_sensitiveDistance * 2);

            updatePortState(port, Port_Type_Input, inputPortRect, mousePoint);
        }
    }

    void GraphicsView::mouseMoveNearByOutputPort(QPointF mousePoint)
    {
        QList<QGraphicsItem *> all = items();
        for (QGraphicsItem * one : all)
        {
            /// get item port part
            GraphicsItem * item = dynamic_cast<GraphicsItem *>(one);
            if (!item) { continue; }
            PortItem * port = item->portPart();
            if (!port) { continue; }

            /// is mouse nearby item output port. manage input port state
            QPointF portPoint = port->scenePos();
            QRectF outputPortRect(
                portPoint.x() + item->boundingRect().width() - _private->_sensitiveDistance,
                portPoint.y() - _private->_sensitiveDistance,
                port->size() + _private->_sensitiveDistance * 2,
                port->size() + _private->_sensitiveDistance * 2);

            updatePortState(port, Port_Type_Output, outputPortRect, mousePoint);
        }
    }

    void GraphicsView::updatePortState(PortItem * port, PortType type, QRectF portRect, QPointF mousePoint)
    {
        GraphicsItem * item = dynamic_cast<GraphicsItem *>(port->parentItem());
        if (!item) { return; }
        bool linked = _private->_mulSimAppDataMgr->isPortLinked(item, type == Port_Type_Input ? true : false);

        /// mouse move nearby port
        if (portRect.contains(mousePoint))
        {
            if (!linked)
                port->setPortState(type, Port_State_Nearby);
            else
                port->setPortState(type, Port_State_Selected_Nearby);
        }
        /// mouse move away port
        else
        {
            if (!linked)
                port->setPortState(type, Port_State_Normal);
            else
                port->setPortState(type, Port_State_Selected);
        }
        port->update();
    }

    void GraphicsView::mousePressSelectInputPort()
    {
        QList<QGraphicsItem *> all = items();
        for (QGraphicsItem * one : all)
        {
            /// get all port type graphics item
            PortItem * port = dynamic_cast<PortItem*> (one);
            if (!port) { continue; }

            /// if press nearby input port
            if (Port_State_Nearby != port->portState(Port_Type_Input) &&
                Port_State_Selected_Nearby != port->portState(Port_Type_Input))
            {
                continue;
            }

            /// check line self
            GraphicsItem * from = _private->_line->fromGraphicsItem();
            GraphicsItem * to = dynamic_cast<GraphicsItem *>(port->parentItem());
            if (!to) { continue; }
            if (from == to)
            {
                LOGW("Can Not Line By Self.");
                return;
            }

            /// check if already has line between this two components
            if (containsLine(from, to))
            {
                /// cancel line
                to->portPart()->setPortState(Port_Type_Input, to->portPart()->portState(Port_Type_Input) == Port_State_Nearby ? Port_State_Normal : Port_State_Selected);
                mousePressCancelLine();

                /// log warning
                QString msg = QString("Already Has Line Between %1 %2 And %3 %4. Can 't Line.").
                    arg(from->componentName()).
                    arg(ADDR(from)).
                    arg(to->componentName()).
                    arg(ADDR(to));
                LOGW(msg);
                return;
            }

            /// has pressed mouse left button nearby input port. remove indicate line item
            to->portPart()->setPortState(Port_Type_Input, to->portPart()->portState(Port_Type_Input) == Port_State_Nearby ? Port_State_Normal : Port_State_Selected);
            mousePressCancelLine();

            /// if from to component can line
            EventOper::GraphicsObjectEventOperator * oper = Core::FITKOperatorRepo::getInstance()->getOperatorT<EventOper::GraphicsObjectEventOperator>("LineState");
            if (!oper) { return; }
            QList<QGraphicsObject *> objs{ from,to };
            oper->setGraphicsObject(objs);
            bool bOk = oper->triggered();
            if (!bOk) { return; }

            /// add line item
            LineItem * line = addLine(from, to);

            /// data operation
            dataUpdateByAddLine(line);

            /// log
            QString log = QString("Line From Component App %1 %2 %3 To %4")
                .arg(line->fromGraphicsItem()->componentName())
                .arg(ADDR(line->fromGraphicsItem()))
                .arg(line->toGraphicsItem()->componentName())
                .arg(ADDR(line->toGraphicsItem()));
            LOG(log);
            return;
        }
    }

    void GraphicsView::mousePressSelectOutputPort()
    {
        QList<QGraphicsItem *> all = items();
        for (QGraphicsItem * one : all)
        {
            PortItem * port = dynamic_cast<PortItem*> (one);
            if (!port) { continue; }

            /// whether press nearby output port
            if (Port_State_Nearby == port->portState(Port_Type_Output) ||
                Port_State_Selected_Nearby == port->portState(Port_Type_Output))
            {
                /// has pressed mouse left button nearby output port
                _private->_state.lineState = Line_Need_Select_To_State;
                if (!_private->_line)
                    _private->_line = new IndicateLineItem();
                _private->_line->setFromGraphicsItem(qobject_cast<GraphicsItem*>(port->parentObject()));
                _private->_line->setPos(port->outputPortPos());
                scene()->addItem(_private->_line);
                break;
            }
        }
    }

    void GraphicsView::mousePressCancelLine()
    {
        if (_private->_line &&
            scene()->items().contains(_private->_line))
        {
            GraphicsItem * item = _private->_line->fromGraphicsItem();
            if (!item) { return; }
            item->portPart()->setPortState(Port_Type_Output, Port_State_Normal);
            _private->_state.lineState = Line_Need_Select_From_State;
            scene()->removeItem(_private->_line);
            delete _private->_line;
            _private->_line = nullptr;
        }
    }

    void GraphicsView::dataUpdateByAddLine(LineItem * line)
    {
        /// create line data
        Data::LineData * lineData = new Data::LineData(line, line->fromGraphicsItem(), line->toGraphicsItem());
        _private->_lineDataMgr->appendDataObj(lineData);
        Data::ChannelDegenerate tool;
        QList<Data::Channel *> channels = tool.degenerate(line->fromGraphicsItem()->componentAppData(), line->toGraphicsItem()->componentAppData());
        for (Data::Channel * channel : channels) {
            lineData->addDegeneratedChannel(channel);
        }

        /// get data as single simulation app data. is null , it 's a multi simulation app data
        Data::SingleSimulationAppData * from = _private->_sglSimAppDataMgr->getSingleAppDataByGraphicsObject(line->fromGraphicsItem());
        Data::SingleSimulationAppData * to = _private->_sglSimAppDataMgr->getSingleAppDataByGraphicsObject(line->toGraphicsItem());
        /// remove single simulation app data. update multi simulation app data.
        if (from)
        {
            /// from item is a single simulation app
            if (to)
            {
                /// to item is a single simulation app. construct multi simulation app data. remove two single simulation app data.
                Data::MultiSimulationAppData * multi = new Data::MultiSimulationAppData();
                Data::ComponentAppData * cpt1 = new Data::ComponentAppData();
                cpt1->copy(from);
                Data::ComponentAppData * cpt2 = new Data::ComponentAppData();
                cpt2->copy(to);

                multi->addComponentAppData(cpt1);
                multi->addComponentAppData(cpt2);
                multi->addLineData(lineData);
                _private->_mulSimAppDataMgr->appendDataObj(multi);

                _private->_sglSimAppDataMgr->removeDataObj(from);
                _private->_sglSimAppDataMgr->removeDataObj(to);
            }
            else
            {
                /// to item is a multi simulation app. add line data and from component app date to to multi simulation app data. 
                Data::MultiSimulationAppData * multi = _private->_mulSimAppDataMgr->getMultiAppDataByGraphicsObject(line->toGraphicsItem());
                Data::ComponentAppData * cpt1 = new Data::ComponentAppData();
                cpt1->copy(from);

                multi->addComponentAppData(cpt1);
                multi->addLineData(lineData);

                _private->_sglSimAppDataMgr->removeDataObj(from);
            }
        }
        else
        {
            /// from item is a multi simulation app.
            if (to)
            {
                /// to item is a single simulation app. add line data and to component app date to from multi simulation app data. 
                Data::MultiSimulationAppData * multi = _private->_mulSimAppDataMgr->getMultiAppDataByGraphicsObject(line->fromGraphicsItem());
                Data::ComponentAppData * cpt2 = new Data::ComponentAppData();
                cpt2->copy(to);

                multi->addComponentAppData(cpt2);
                multi->addLineData(lineData);

                _private->_sglSimAppDataMgr->removeDataObj(to);
            }
            else
            {
                /// to item is a multi simulation app . combine two multi simulation app data
                Data::MultiSimulationAppData * multi1 = _private->_mulSimAppDataMgr->getMultiAppDataByGraphicsObject(line->fromGraphicsItem());
                Data::MultiSimulationAppData * multi2 = _private->_mulSimAppDataMgr->getMultiAppDataByGraphicsObject(line->toGraphicsItem());
                multi1->combine(multi2);
                multi1->addLineData(lineData);
                _private->_mulSimAppDataMgr->removeDataObj(multi2);
            }
        }
    }

    void GraphicsView::dataUpdateByRemoveItems(QList<QGraphicsItem *> objs)
    {
        /// dynamic update simulation app data
        QList<Data::ComponentAppData *> singles = _private->_mulSimAppDataMgr->dataUpdateByRemoveItems(objs);
        _private->_sglSimAppDataMgr->dataUpdateByRemoveItems(objs);
        for (Data::ComponentAppData * single : singles)
        {
            Data::SingleSimulationAppData * app = new Data::SingleSimulationAppData();
            app->copy(single);
            _private->_sglSimAppDataMgr->appendDataObj(app);
        }

        /// remove direct removed item and line
        for (QGraphicsItem * obj : objs)
        {
            GraphicsItem * item = dynamic_cast<GraphicsItem *>(obj);
            if (item)
            {
                Data::ComponentAppData * data = _private->_cptAppDataMgr->componentAppDataByGraphicsObject(item);
                if (!data) { continue; }
                _private->_cptAppDataMgr->removeDataObj(data);
            }
            else
            {
                LineItem * line = dynamic_cast<LineItem *>(obj);
                if (!line) { continue; }
                _private->_lineDataMgr->removeLineDataByGraphicsItem(line);
            }
        }

        /// remove no from or no to item line
        QList<QGraphicsItem *> all = scene()->items();
        for (QGraphicsItem * removed : objs)
            all.removeOne(removed);
        _private->_lineDataMgr->removeInvalidData(all);
    }

    QString GraphicsView::loadQSS(const QString & resource)
    {
        QString qss;
        QFile file(resource);
        if (file.open(QIODevice::ReadOnly))
        {
            qss = file.readAll();
            file.close();
        }
        return qss;
    }

    bool GraphicsView::containsLine(GraphicsItem * from, GraphicsItem * to)
    {
        int num = _private->_lineDataMgr->getDataCount();

        for (int index = 0; index < num; ++index)
        {
            Data::LineData * lineData = _private->_lineDataMgr->getDataByIndex(index);
            LineItem * line = dynamic_cast<LineItem *> (lineData->graphicsObject());
            if (!line) { continue; }
            if (line->fromGraphicsItem() == from &&
                line->toGraphicsItem() == to)
            {
                return true;
            }
        }
        return false;
    }

    LineItem * GraphicsView::addLine(GraphicsItem * from, GraphicsItem * to)
    {
        LineItem * line = new LineItem();
        line->setFromGraphicsItem(from);
        line->setToGraphicsItem(to);
        from->portPart()->setPortState(Port_Type_Output, Port_State_Selected);
        to->portPart()->setPortState(Port_Type_Input, Port_State_Selected);
        line->setZValue(0);
        scene()->addItem(line);
        return line;
    }

}